﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Security.Principal;
using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Novell.Directory.Ldap;

namespace HiP.Infrastructure.LDAP
{
    /// <summary>
    /// 活动目录辅助类。封装一系列活动目录操作相关的方法。
    /// </summary>
    public sealed class ADHelper
    {
          LdapSettings _ldapSettings =new LdapSettings();

        private  readonly string[] _attributes =
        {
            "objectSid", "objectGUID", "objectCategory", "objectClass", "memberOf", "name", "cn", "distinguishedName",
            "sAMAccountName", "sAMAccountName", "userPrincipalName", "displayName", "givenName", "sn", "description",
            "telephoneNumber", "mail", "streetAddress", "postalCode", "l", "st", "co", "c","department","pager","company"
        };

        public ADHelper( )
        {
            _ldapSettings.BaseDn = "ou=hi-p,dc=hi-p,dc=com";
            _ldapSettings.Domain = "hi-p";
            _ldapSettings.ServerAddress = "10.0.3.11";
            _ldapSettings.ServerPort = 389;
            _ldapSettings.ServerPortSsl = 636;
            _ldapSettings.RootUserAccount = "ldap";
            _ldapSettings.RootUserPassword = "pass4ldap";

        }




        #region 基本增删改查
        public  LdapEntry Get(string dn, string[] attrs = null)
        {
            using var conn = new LdapConnection();
            Init(conn);
            var entry = conn.Read(dn, attrs);
            return entry;
        }

        public  IList<LdapEntry> SearchByFilter(string filter, string[] attrs = null)
        {
            //todo: 有问题待修复，当内容太多时候会遭遇zize limit错误，需分页解决
            if (attrs == null)
            {
                attrs = _attributes;
            }
            using var conn = new LdapConnection();
            Init(conn);
            var lsc = conn.Search(_ldapSettings.BaseDn, LdapConnection.ScopeSub, filter, attrs, false);
            var entities = lsc.ToList();
            return entities;

        }
        public void AddUser(LdapUser user, string password)
        {
            var dn = $"CN={user.FirstName} {user.LastName},{_ldapSettings.BaseDn}";

            var attributeSet = new LdapAttributeSet
            {
                new LdapAttribute("instanceType", "4"),
                new LdapAttribute("objectCategory", $"CN=Person,CN=Schema,CN=Configuration,{_ldapSettings.DomainDn}"),
                new LdapAttribute("objectClass", new[] {"top", "person", "organizationalPerson", "user"}),
                new LdapAttribute("name", user.UserName),
                new LdapAttribute("cn", $"{user.FirstName} {user.LastName}"),
                new LdapAttribute("sAMAccountName", user.UserName),
                new LdapAttribute("userPrincipalName", user.UserName),
                new LdapAttribute("userPassword",password),
                new LdapAttribute("unicodePwd", Convert.ToBase64String(Encoding.Unicode.GetBytes($"\"{user.Password}\""))),
                new LdapAttribute("userAccountControl", user.MustChangePasswordOnNextLogon ? "544" : "512"),
                new LdapAttribute("givenName", user.FirstName),
                new LdapAttribute("sn", user.LastName),
                new LdapAttribute("mail", user.EmailAddress)
            };

            if (user.DisplayName != null)
            {
                attributeSet.Add(new LdapAttribute("displayName", user.DisplayName));
            }

            if (user.Description != null)
            {
                attributeSet.Add(new LdapAttribute("description", user.Description));
            }
            if (user.Phone != null)
            {
                attributeSet.Add(new LdapAttribute("telephoneNumber", user.Phone));
            }
            if (user.Address?.Street != null)
            {
                attributeSet.Add(new LdapAttribute("streetAddress", user.Address.Street));
            }
            if (user.Address?.City != null)
            {
                attributeSet.Add(new LdapAttribute("l", user.Address.City));
            }
            if (user.Address?.PostalCode != null)
            {
                attributeSet.Add(new LdapAttribute("postalCode", user.Address.PostalCode));
            }
            if (user.Address?.StateName != null)
            {
                attributeSet.Add(new LdapAttribute("st", user.Address.StateName));
            }
            if (user.Address?.CountryName != null)
            {
                attributeSet.Add(new LdapAttribute("co", user.Address.CountryName));
            }
            if (user.Address?.CountryCode != null)
            {
                attributeSet.Add(new LdapAttribute("c", user.Address.CountryCode));
            }

            var newEntry = new LdapEntry(dn, attributeSet);
            Add(newEntry);
        }

        public  void Add(LdapEntry newEntry)
        {
            #region 构造LdapEntry示例
            //var cn = Guid.NewGuid().ToString();
            //var attributeSet = new LdapAttributeSet
            //{
            //    new LdapAttribute("cn", cn),
            //    new LdapAttribute("objectClass", TestsConfig.DefaultObjectClass),
            //    new LdapAttribute("givenName", "Lionel"),
            //    new LdapAttribute("sn", "Messi"),
            //    new LdapAttribute("mail", cn + "@gmail.com"),
            //    new LdapAttribute("userPassword", TestsConfig.DefaultPassword)
            //};

            //var dn = $"cn={cn}," + BaseDn;
            //return new LdapEntry(dn, attributeSet); 
            #endregion
            using (var conn = new LdapConnection())
            {
                Init(conn);
                conn.Add(newEntry);
            }
        }


        public  void Modify(string entryDn, string attrName, string value)
        {
            using var conn = new LdapConnection();
            Init(conn);
            var newAttribute = new LdapAttribute(attrName, value);
            var modification = new LdapModification(LdapModification.Add, newAttribute);
            conn.Modify(entryDn, modification);

        }

        private  void Init(LdapConnection conn)
        {
            conn.Connect(_ldapSettings.ServerAddress, _ldapSettings.ServerPort);
            conn.Bind(_ldapSettings.Domain + "\\" + _ldapSettings.RootUserAccount, _ldapSettings.RootUserPassword);
        }

        public  void DeleteEntry(string dn)
        {
            using (var conn = new LdapConnection())
            {
                Init(conn);
                conn.Delete(dn);
            }
        }
        #endregion

        #region 常用操作

        public  LdapEntry GetByGuid(Guid guid)
        {
            string strGUID = ConvertGuidToOctetString(guid);
            var queryString = $"objectGUID;binary={strGUID}";
            var entities = SearchByFilter(queryString);
            try
            {
                return entities.Single();
            }
            catch (Exception ex)
            {
                throw new Exception($"未能通过Guid{guid}找到entry或者找到多条");
            }
        }

        private  string ConvertGuidToOctetString(Guid guid)
        {
            byte[] byteGuid = guid.ToByteArray();

            string queryGuid = "";

            foreach (byte b in byteGuid)
            {
                queryGuid += @"\" + b.ToString("x2");
            }

            return queryGuid;
        }

        /// <summary>
        /// 通过账号、Guid、DistinguishedName这些标识获取唯一对象
        /// </summary>
        /// <param name="identity"></param>
        /// <returns></returns>
        public  LdapEntry GetByIdentity(string identity)
        {
            if (Guid.TryParse(identity, out Guid guid))
            {
                return GetByGuid(guid);
            }
            var queryString = string.Format("(&" +
                                                "(objectClass=user)" +
                                                //"(sAMAccountType=805306368)" +
                                                "(|" +
                                                        "(sAMAccountName={0})" +
                                                        "(DistinguishedName={0})" +
                                                ")" +
                                            ")", identity);

            var entities = SearchByFilter(queryString);
            try
            {
                return entities.Single();
            }
            catch (Exception ex)
            {
                throw new Exception($"未能通过标识{identity}找到entry或者找到多条");
            }
        }


        /// <summary>
        /// 得到所有帐号类型为用户的集合
        /// </summary>
        /// <returns></returns>
        public  IList<LdapEntry> GetAllUsers()
        {
            var queryString = "(&(objectCategory=person)(objectClass=user))";
            var entities = SearchByFilter(queryString);
            return entities;
        }


        /// <summary>
        /// 根据用户显示名、帐号、工号模糊匹配取得用户的对象
        /// </summary>
        /// <param name="keyword"></param>
        /// <returns></returns>
        public  IList<LdapEntry> SearchByKeyword(string keyword)
        {
            var queryString = string.Format("(&" +
                                                "(objectClass=user)" +
                                                //"(sAMAccountType=805306368)" +
                                                "(|" +
                                                    "(|" +
                                                        "(displayName=*{0}*)" +
                                                        "(sAMAccountName=*{0}*)" +
                                                    ")" +
                                                    "(pager=*{0}*)" +
                                                ")" +
                                            ")", keyword);
            return SearchByFilter(queryString);
        }

        public  bool VerifyPassword(string account, string password)
        {
            try
            {
                using (var conn = new LdapConnection())
                {
                    conn.Connect(_ldapSettings.ServerAddress, _ldapSettings.ServerPort);
                    conn.Bind(_ldapSettings.Domain + "\\" + account, password);
                }
                return true;
            }
            catch (LdapException ex)
            {
                if (ex.ResultCode == LdapException.InvalidCredentials)
                {
                    return false;
                }

                throw ex;
            }
        }


        public  void ModifyPassword(string entryDn, string value)
        {
            using (var conn = new LdapConnection())
            {
                Init(conn);
                Modify(entryDn, "userPassword", value);
            }
        }


        #endregion

    }

}
